package com.example.dishit.model.dbproxy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executors;

import com.example.dishit.model.dish.DishData;

public class FakeDBProxy implements IDBProxy {
    
    private final Set<String> myDishers = new TreeSet<String>();
    {
        myDishers.add("Jim");
        myDishers.add("Ryan");
        myDishers.add("Nicci");
        myDishers.add("Pete");
        myDishers.add("Mark");
        myDishers.add("Dan");
        myDishers.add("Devin");
        myDishers.add("Brian");
        myDishers.add("Bobbie");
        myDishers.add("Barclay");
        myDishers.add("Dave");
        myDishers.add("Rich");
        myDishers.add("Raol");
        myDishers.add("Carl");
        myDishers.add("Alex");
    }
    
    private final List<String> otherExistingUsers = Arrays.asList("Alice", "Bob", "Cameron", "Dennis", "Erin", "Frank", "George", "Harry", "Ivan", "Jose");
    private final List<String> allUsers = new ArrayList<String>();
    {
        allUsers.addAll(myDishers);
        allUsers.addAll(otherExistingUsers);
    }
    
    public List<String> getMyDishers(String myUserId) {
        final List<String> retList = new ArrayList<String>();
        for(final String disher : myDishers) {
            retList.add(disher);
        }
        return retList;
    }
    
    public void getMyDishers(final String myUserId, final IDBProxyCallback callback) {
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            public void run() {
                final List<String> result = getMyDishers(myUserId);
                callback.onDishersCallback(result);
            }
        });
    }
    
    public String followerUser(String thisUserId, String userIdToFollow) {
        if(userIdToFollow == null || userIdToFollow.isEmpty()) return Boolean.FALSE.toString();
        else if(!allUsers.contains(userIdToFollow)) return Boolean.FALSE.toString();
        else return Boolean.toString(myDishers.add(userIdToFollow));
    }
    
    public void followerUser(final String thisUserId, final String userIdToFollow, final IDBProxyCallback callback) {
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            public void run() {
                final String result = followerUser(thisUserId, userIdToFollow);
                
                // FIXME: How to use result to determine this?
                final boolean wasSuccessful = false;
                
                callback.onResultCallback(wasSuccessful, result);
            }
        });
    }
    
    public String unfollowUser(String thisUserId, String userIdToUnfollow) {
        System.out.println("FakeDBProxy: unfollowUser(), thisUserId = " + thisUserId + "   userIdsToUnfollow = " + userIdToUnfollow);
        
        if(userIdToUnfollow == null || userIdToUnfollow.isEmpty()) return Boolean.FALSE.toString();
        else return Boolean.toString(myDishers.remove(userIdToUnfollow));
    }
    
    public void unfollowUser(final String thisUserId, final String userIdToUnfollow, final IDBProxyCallback callback) {
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            public void run() {
                final String result = unfollowUser(thisUserId, userIdToUnfollow);
                
                // FIXME: How to use result to determine this?
                final boolean wasSuccessful = false;
                
                callback.onResultCallback(wasSuccessful, result);
            }
        });
    }
    
    //==========================================================
    
    private class DishDataWrapper implements Comparable<DishDataWrapper> {
        private final long timeArrivedInDbMillis;
        private final DishData data;
        
        public DishDataWrapper(final long timeEnteredMillis, final DishData data) {
            this.timeArrivedInDbMillis = timeEnteredMillis;
            this.data = data;
        }
        
        public int compareTo(final DishDataWrapper arg0) {
            if(timeArrivedInDbMillis > arg0.timeArrivedInDbMillis) return -1;
            else if(timeArrivedInDbMillis < arg0.timeArrivedInDbMillis) return 1;
            else return 0;
        }
    }
    
    private Queue<DishDataWrapper> receivedDishes = new PriorityQueue<DishDataWrapper>();
    {
        final DishData dish1 = generateDish("ttebow15");
        final DishData dish2 = generateDish("jnoah13");
        final DishData dish3 = generateDish("pmanning18");
        receivedDishes.offer(new DishDataWrapper((System.currentTimeMillis()-5000L), dish1));
        receivedDishes.offer(new DishDataWrapper((System.currentTimeMillis()-5000L), dish2));
        receivedDishes.offer(new DishDataWrapper((System.currentTimeMillis()-5000L), dish3));
    }
    
    private List<DishData> sentDishes = new ArrayList<DishData>();
    
    public void storeReceivedDish(final String myUserId, final DishData data, final IDBProxyCallback callback) {
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            public void run() {
                // (Use "0" because this Dish has already been acknowledged.)
                final boolean success = receivedDishes.offer(new DishDataWrapper(0, data));
                
                callback.onResultCallback(success, "Yes, stored DishData in DB.");
            }
        });
    }
    
    public List<DishData> getLatestReceivedDishes(String myUserId, long afterThisTimeMillis) {
        final List<DishData> snapshot = new ArrayList<DishData>();
        
        for(final DishDataWrapper wrapper : receivedDishes) {
            if(wrapper.timeArrivedInDbMillis > afterThisTimeMillis) {
                snapshot.add(wrapper.data);
            }
        }
        
        Collections.sort(snapshot, new Comparator<DishData>() {
            public int compare(DishData arg0, DishData arg1) {
                if(arg0.getTimeOfConstructionMillis() > arg1.getTimeOfConstructionMillis()) return -1;
                else if(arg0.getTimeOfConstructionMillis() > arg1.getTimeOfConstructionMillis()) return 1;
                else return 0;
            }
        });
        return snapshot;
    }
    
    public String broadcastDish(DishData data) {
        final boolean success = sentDishes.add(data);
        
        if(success) {
            receivedDishes.offer(new DishDataWrapper(System.currentTimeMillis(), data));
        }
        
        return Boolean.toString(success);
    }
    
    public List<DishData> getLatestSentDishes(String myUserId, long afterThisTimeMillis) {
        final List<DishData> snapshot = new ArrayList<DishData>();
        for(final DishData dish : sentDishes) {
            snapshot.add(dish);
        }
        return snapshot;
    }
    
    private static DishData generateDish(final String recommendedBy) {
        final List<String> imageUrls = new ArrayList<String>();
        imageUrls.add("http://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Outback_Steakhouse_CA.JPG/225px-Outback_Steakhouse_CA.JPG");
        imageUrls.add("http://www.outback.com/menu/images/top.jpg");
        imageUrls.add("http://www.outback.com/menu/images/bloomin-onion_t.jpg");
        imageUrls.add("http://www.outback.com/menu/images/alice-springs-chicken-quesadilla_t.jpg");
        imageUrls.add("http://www.outback.com/menu/images/grilled-shrimp-on-the-barbie_t.jpg");
        imageUrls.add("http://www.outback.com/menu/images/wings_t.jpg");
        
        final List<String> comments = new ArrayList<String>();
        comments.add(recommendedBy + ":    Look at what I say oh.  The more I see the less I know that more I like to let it go.  Hey oh, woah oh oh.  Deep beneah the cover of another perfect wonder where it's all white as snow.  Narrowly divided by a world so undecided and there's no where to go.  In between the cover of another perfect wonder.");
        
        // Return fake recommendation.
        return new DishData.Builder()
                    .recommendedBy(recommendedBy)
                    .restaurantName("Outback Steakhouse")
                    .priceMin(23.15f)
                    .priceMax(40.02f)
                    .imageUrls(imageUrls)
                    .keywords(Arrays.asList("seafood", "expensive", "seattle", "waterview", "downtown", "hang out", "Seattle", "happy hour"))
                    .reasons(Arrays.asList("like seafood", "highly rated", "friends recommended it", "great appetizers", "local cuisine", "like beer"))
                    .rating(2.4f)
                    .comments(comments)
                    .websiteUrl("http://outback.com")
                    .mapUrl("https://maps.google.com/maps?client=ubuntu&channel=fs&q=university+of+florida+to+outback+steahouse+gainesville+fl&oe=utf-8&um=1&ie=UTF-8&hl=en&sa=N&tab=wl")
                    /*.mapUrl("google.navigation:q=an+address+city")*/
                    /*.mapUrl("geo:0,0?q=37.423156,-122.084917 (" + "evan" + ")")*/
                    .streetAddress("123 Fake Street")
                    .city("Gainesville")
                    .state("FL")
                    .zipcode("12345-6789")
                    .phoneNumberDigits("1234567890")
                    .build();
    }
    
    public void getLatestReceivedDishes(final String myUserId, final long afterThisTimeMillis, final IDBProxyCallback callback) {
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            public void run() {
                final List<DishData> result = getLatestReceivedDishes(myUserId, afterThisTimeMillis);
                callback.onDishDataCallback(result);
            }
        });
    }
    
    public void getLatestSentDishes(final String myUserId, final long afterThisTimeMillis, final IDBProxyCallback callback) {
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            public void run() {
                final List<DishData> result = getLatestSentDishes(myUserId, afterThisTimeMillis);
                callback.onDishDataCallback(result);
            }
        });
    }
    
    public void broadcastDish(final DishData data, final IDBProxyCallback callback) {
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            public void run() {
                final String result = broadcastDish(data);
                final boolean wasSuccessful = "true".equals(result);
                callback.onResultCallback(wasSuccessful, result);
            }
        });
    }
}
